查看原文
其他

机器学习三人行(系列三)----end-to-end机器学习

Rhinoceros 智能算法 2021-09-10

系列二我们详细介绍了数据下载,数据透析以及数据的不同分组方式,详情请参考:机器学习三人行(系列二)----机器学习前奏,洞悉数据之美!。但是在真正进行训练之前,我们还需要对数据进行一个预处理。因为有些数据有一些缺失,以及怎么处理分类变量,归一化之类的操作,这样算法才能够表现出更好的性能。通常预处理有以下几个步骤:缺失值和离群值处理(看情况),特征表征(使模型理解数据),归一化,那么我们接着系列二一起继续学习。


一、数据清洗

    再次观察数据集,通过housing.shape,可以查看数据集大小(16512, 13),数据集的特征如下:

发现totals_rooms和bedrooms_per_room存在数据缺失的情况,由于一些机器学习算法无法计算缺失数据,因此需要对缺失值适当的处理工作。

1.1 缺失值处理

常用处理方法:对丢弃包含缺失值的行或列、对缺失值进行填充。我们来查看缺失的占比情况:

发现只有少部分缺失,因此丢弃行或列是不合适的,不然会损失有用的信息,因此考虑对缺失值进行填充,而填充又包含填充中位数,众数,均值,常量值(如-1),多值插补或通过建模对缺失值进行预测等方法。考虑本例只有少部分缺失值,因此使用中位数填充,方法:

       from sklearn.preprocessing import Imputer

imputer = Imputer(strategy="median")

imputer.fit(housing_num)

考虑中位数填充缺失值只能对数值处理,对object的ocean_proximity是无效的。因此将数值型相关特征从数据集中抽出来,housing_num = housing.drop('ocean_proximity', axis=1)。

       接下来可以对housing_num做缺失值处理了,X = imputer.transform(housing_num) ,X就是处理后的结果生成一个新的dataframe,如下:

housing_tr = pd.DataFrame(X, columns=housing_num.columns,

                    index = list(housing.index.values))

查看生成housing_tr的处理后结果。

2.2 分类变量

    housing数据集中唯一的分类特征ocean_proximity,将ocean_proximity特征拿出来处理,housing_cat = housing['ocean_proximity'],查看类别的构成如下:

由于机器学习算法是没有办法理解字符的分类变量的,因此需要将字符的分类特征转换成数值型分类特征,我们采用pandas中factorize()来完成转换

虽然完成数值型分类特征的转换,但是这样处理会引入一个问题,转换的分类变量变成有序和可比较了,即,转换后的0,1,2,3,4,是不是能说0,1变量的距离和1,2的距离是一样的。这样明显和原来的分类的特性是不符合的,要消除这个问题,需要使用OneHotEncoder来处理。

其实上面的从factorize到One-Hot encode可以使用sklearn.preprocessing.LabelBinarizer一步完成

因此类似One-Hot也会存在一个问题,如果分类变量的类别过多时,会导致出现维度灾难。

2.3 特征归一化

    由于特征不同的分布范围,对一些算法寻优速度和收敛有影响,比如SVM,因此再进入模型前需要对特征进行归一化,常用的归一化有:Min-Max Scaling和Z-Score Standardization两种。分别对应sklearn中MinMaxScaler和StandardScaler方法。而且StandardScaler更多是应用在特征存在偏态分布的情况下,根据系列二中特征的分布情况,这里采用StandardScaler进行特征归一化。



二、pipeline流水线

      通过上面的处理,我们已经对数据进行了基本的数据清洗工作,我们可以拿上面处理好的数据直接进行建模了,但是这里会面临两个问题:一个是我们是把数值型特征和分类型特征分别处理的,因此需要合并,二是后面对于验证集的预测前,验证数据集需要按照的处理流程重新跑一遍,才能进行预测,就需要将训练集的一些特殊的处理过程保存,应用到验证集和测试集的处理上。

       想想如果需要对训练集的处理方法修改的话,将是更加麻烦的事情,这里就需要引入了sklearn中一个强大的工具,pipeline,把我们的处理过程流水线化,对验证集和测试集上调用fit、transform就可以完成处理,然后扔到模型中去预测,而且配合FeatureUnion还可以完成并行处理。过程大致如下:

2.1 自定义方法

sklearn的pipeline要完成流程化处理,调用的函数必须包含三种方法:fit(),transform(),和fit_transform。而且是只要包含这三种方法都可以放到pipeline中去,也就是说,我们可以自己写方法,只需要定义好这三个方法就可以了,最后一个fit_transform方法,只需要通过继承TransformerMixin方法就可以获得,因此只需要我们定义fit和transform方法就可以了,可以把系列二中特征融合自己定义一个pipeline方法:

      attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)

housing_extra_attribs = attr_adder.transform(housing.values)

通过上面调用测试方法是否可用。

2.2 sklearn可用方法

sklearn本身包含了丰富的预处理和特征处理的方法,比如在预处理中使用到的StandardScaler,Imputer等,下面列举了一些sklearn常用的处理方法,更多内容可以查看sklearn的官网:

2.3 本文使用方法

通过上面的方法可以完成将前面对数值型特征的处理写成一个pipeline:

由于我们的处理分成的数值型特征处理和分类特征的处理两部分,如果写成两个pipeline来分别处理的话,最后需要将结果合并,还是太麻烦了,因此我们还需要一个对选择数据集的pipeline处理方法:

有了DataframeSelector方法,我们通过FeatureUnion的并行化处理来写一个完整的pipeline处理流程如下:

通过调用:housing_prepared = full_pipeline.fit_transform(housing)完成全部的处理过程。


三、建立模型

     3.1 基准模型

通过上面full_pipeline,数据的预处理已经完成,接下来利用处理好的数据建立模型,我们选择简单的线性回归,作为模型的baseline,后续在此基础上优化模型效果。

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()

lin_reg.fit(housing_prepared, housing_labels)

我们尝试取原数据前五条数据来预测,观测效果

some_data = housing.iloc[:5]

some_labels = housing_labels.iloc[:5]

some_data_prepared = full_pipeline.transform(some_data)

print("Predictions:", lin_reg.predict(some_data_prepared))

预测结果:

Predictions: [ 210644.60459286  317768.80697211  210956.43331178   59218.98886849  189747.55849879]

而原数据的实际值为:

Actual: [286600.0, 340600.0, 196900.0, 46300.0, 254500.0]

对于回归问题,常用的评估指标有均方根误差(RMSE),平均绝对差值(MAE),RMSE在可以使用sklearn的mean_squared_error计算,MAE对应mean_absolute_error。对测试的预测值计算评估指标结果如下:

在此基础上可以尝试各种不同的模型,通过比较评估指标,获取最合适的模型,作为最终的建模模型,当然上面的评估只是作为模型预测的测试,实际中我们需要将数据集划分出训练集和验证集,通过交叉验证的方法来评估。

     3.2交叉验证

     交叉验证,可以使用sklearn的cross_val_score来完成,计算之前先写一个结果的展示函数,方便查看结果:

     def display_scores(scores):

      print("Scores:", scores)

   print("Mean:", scores.mean())

   print("Standard deviation:", scores.std())

   接下来我们通过建立随机森林来,通过交叉验证来评估模型效果;

构建模型:

效果评估:

通过10折交叉验证,得到最终的平均RMSE值:52564.19。通过类似方法可以比较不同的模型的预测效果。


四、参数调整

     当我们使用机器学习算法时,会发现几乎所有的算法都包括一些超参数,这些参数和常规参数不同,它们不是模型的一部分,不会在模型拟合中被自动调整。它们是在另外的步骤中被调整的。一些超参数的例子,包括在岭回归和lasso回归中的正则项lambda、支持向量机中的C项、基于树的算法中树的数量(如,随机森林、梯度提升机)。

    常用的超参数优化方法有:网格搜索,随机搜索,贝叶斯优化,sklearn已经为我们提供了网格搜索和随机搜索的方法实现,贝叶斯优化也有一些的实现包,而且贝叶斯优化在一些数据挖掘竞赛kaggle中用的比较多。

   4.1. 网格搜索

    网格搜索是在我们预先设定的参数的不同超参数取值中,组成出最优结果的超参数方法,如对上面的随机森林算法寻找最优参数  

上面的网格搜索会对12种n_estimators和max_features的组合建模和6种boostrap的参数组合来寻找最优参数,也就是系统会自动建立18个模型,自动比较出最优的参数,最后通过grid_search.best_params_可以查看最优的模型参数。

    4.2. 随机搜索  

  随机搜索的思想和网格搜索比较相似,只是不再搜索上界和下界之间的所有值,只是在搜索范围中随机取样本点。它的理论依据是,如果随即样本点集足够大,那么也可以找到全局的最大或最小值,或它们的近似值。通过对搜索范围的随机取样,随机搜索一般会比网格搜索要快一些。但是和网格搜索的快速版(非自动版)相似,结果也是没法保证的。下面通过随机搜索来寻找最优参数。

使用随机搜索还有一个好处是我们可以设定搜索迭代的次数来控制对调参的资源分配。

    4.3. 贝叶斯优化

      贝叶斯优化寻找使全局达到最值的参数时,使用了和网格搜索、随机搜索完全不同的方法。网格搜索和随机搜索在测试一个新的点时,会忽略前一个点的信息。而贝叶斯优化充分利用了这个信息。贝叶斯优化的工作方式是通过对目标函数形状的学习,找到使结果向全局最大值提升的参数。它学习目标函数形状的方法是,根据先验分布,假设一个搜集函数。在每一次使用新的采样点来测试目标函数时,它使用这个信息来更新目标函数的先验分布。然后,算法测试由后验分布给出的,全局最值最可能出现的位置的点。关于贝叶斯优化的原理可以在公众号中回复"贝叶斯优化",可以获取到相关论文。

关于该方法的调参在github上已经有人根据论文内容,把算法实现了,而且在kaggle比赛中得到广泛使用。它的python包名叫bayes_opt。可以通过pip install bayesian-optimization来安装。安装成功后可以使用bayes_opt来进行参数优化,使用实例如下:

通过robf.maximize(n_iter=10)查看优化过程并获取最优值,由于bayes_opt只能求最大值,因此rfccv输出需要转换成10000/np.sqrt(-val)。

对于贝叶斯优化,一个主要需要注意的地方,是一旦它找到了一个局部最大值或最小值,它会在这个区域不断采样,所以它很容易陷入局部最值。为了减轻这个问题,贝叶斯优化算法会在勘探和开采(exploration and exploitation)中找到一个平衡点。勘探(exploration),就是在还未取样的区域获取采样点。开采(exploitation),就是根据后验分布,在最可能出现全局最值的区域进行采样。



五、模型评估

   通过模型的调参,获取最优参数,利用最优参数建立我们最终的模型。前面我们是在训练集和验证集训练得到最优模型,但是最终决定模型是否可用,是需要通过评估模型在测试集上的预测表现,这是我们的最终一步,通过比较模型在测试集的表现决定模型的效果,正常来说,测试集的表现会比验证集上的表现稍差,但是差别不会太大,如果模型的预测差别很大的,那么可能由两个原因导致:1、出现过拟合现象,2、测试集和验证集特征分布有较大差别。因此需要从这两个方面进行排查,另外,看到模型在测试集上效果不佳,千万不要基于测试集来进行调参以达到模型在测试集上的表现,这样并不能提高模型的泛化能力,而只是测试集又变成了验证集,再后续新的测试集上还是会出现同样的问题。


(如需更好的了解相关知识,欢迎加入智能算法社区,在“智能算法”公众号发送“社区”,即可加入算法微信群和QQ群)

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存